/**
 * Created with IntelliJ IDEA.
 * User: zzl
 * Date: 12-12-6
 * To change this template use File | Settings | File Templates.
 */
(function ($, undefined) {

    var PopupMenu = function (ul, options) {
        this.$ul = $(ul);
        this.options = options;
        this.setup();
    };

    var proto = { constructor: PopupMenu };
    PopupMenu.prototype = proto;

    var clickingByCode = false;

    proto.setup = function () {
        var me = this;

        me.$ul.addClass('menu');

        me.$ul.find('li').each(function () {
            var $li = $(this);
            if (jsex.trim($li.text()) == '-') {
                $li.html('<span></span>').addClass('separator');
            }
            //
            if ($li.children('ul').length) {
                me.setupParentMenuItem($li);
            }
            else {
//                me.setupLeafMenuItem($li);
            }
            if ($li.data('checked')) {
                $('<div class="checkbox"></div>').prependTo($li);
            }
            if ($li.children('img.icon').length) {
                $li.addClass('icon');
                $li.parent().addClass('icon');
            }
            if ($li.data('disabled')) {
                $li.addClass('disabled');
            }
            if ($li.data('hidden')) {
                $li.hide();
            }

        });

        //
        me.$ul.on('mouseenter', 'li', function (e) {
            me.li_mouseEnter($(this), e);
        });
        me.$ul.on('mouseleave', 'li', function (e) {
            me.li_mouseLeave($(this), e);
        });
        me.$ul.on('click', 'li', function (e) {
            me.li_click($(this), e);
        });
        $(document).on('mousedown.popupMenu', function (e) {
            me.document_mousedown(e);
        });
        if (!me.options.rootVisible) {
            me.$ul.hide();
        }
        //
        me.$ul.on('setCheckedRequest', function (e, checked) {
            var li = e.target;
            me.setChecked(li, checked);
        });
        me.$ul.on('setDisabledRequest', function (e, disabled) {
            var li = e.target;
            me.setDisabled(li, disabled);
        });
    };

    proto.document_mousedown = function (e) {
        var me = this;
        if ($.contains(me.$ul[0], e.target)) {
            return;
        }
        me.hide();
    };

    proto.hide = function () {
        var me = this;
        me.$activeMenu = null;
        me.hideMenu();
        me.$ul.find('li.hovered').removeClass('hovered');

        if (!me.options.rootVisible) {
            me.$ul.hide();
        }
//        $(document).off('keydown.popupMenu');
        undecorated.keydownInterceptors = []; // temp
    };

    proto.li_click = function ($li, e) {
        var me = this;
        if (clickingByCode) {
            return;
        }
        e.stopPropagation();
        if ($(e.target).is('a')) {
            e.preventDefault();
        }

        me.triggerAction($li);
    };

    proto.triggerAction = function($li) {
        var me = this;
        var $a = $li.children('a');
        if ($a.length && !$li.is('.disabled')) {
            clickingByCode = true;
            $a[0].click();
            clickingByCode = false;
        }

        if ($li.data('autocheck')) {
            me.setChecked($li[0], !$li.data('checked'));
        }
        if (me.options.hideOnAction && !$li.hasClass('hasMenu') && !$li.hasClass('separator')) {
            me.hide();
        }
        $li.trigger('action', [$li]);
    };

    proto.li_mouseEnter = function ($li, e) {
        var me = this;
        e.stopPropagation();
        $li.addClass('hovered');
        me.options.trace && console.log('li_mouseEnter ' +
            $li.contents('label:first').text() + ' @ ' + new Date());

        me.cancelShowMenu();
        if (me.$lastActiveMenu) {
            me.$lastActiveMenu.removeClass('active');
        }
        //
        if ($li.hasClass('menuShown')) {
            me.cancelHideMenu();
            return;
        }

        me.$lastActiveMenu = me.$activeMenu;
        me.$activeMenu = $li.parent();
        me.$activeMenu.addClass('active');

        console.log('mouseEnter ' + $li.find('label:first').text() + '... @' + new Date());
        if (me.$lastShownMenu) {
            if (me.$lastShownMenu.is(me.$activeMenu)) {
                me.cancelHideMenu();
            }
            else {
                me.createHideMenuFuture();
            }
        }

        if ($li.hasClass('hasMenu') && !$li.is('.disabled')) {
            me.createShowMenuFuture($li);
        }

    };

    proto.li_mouseLeave = function ($li, e) {
        var me = this;
        $li.removeClass('hovered');
        me.options.trace && console.log('li_mouseLeave @ ' + new Date());
        // cancel show menu on leaving the triggering li
        if (me.showMenuFuture && me.showMenuFuture.$li.is($li)) {
            me.cancelShowMenu();
        }
    };

    proto.createShowMenuFuture = function ($li) {
        var me = this;
        me.options.trace && console.log('createShowMenuFuture @ ' + new Date());
        var timer = setTimeout(function () {
            me.hideMenuNow();
            me.showMenu($li);
        }, me.options.showDelay);
        me.showMenuFuture = {
            timer: timer,
            $li: $li
        }
    };

    proto.createHideMenuFuture = function () {
        var me = this;
        //
        me.options.trace && console.log('createHideMenuFuture @ ' + new Date());
        if (me.hideMenuFuture) {
            clearTimeout(me.hideMenuFuture.timer);
        }
        var timer = setTimeout(function () {
            me.hideMenu();
        }, me.options.hideDelay);
        me.hideMenuFuture = {
            timer: timer
        };
    };

    proto.hideMenuNow = function () {
        var me = this;
        me.options.trace && console.log('hideMenuNow @ ' + new Date());
        if (me.hideMenuFuture) {
            clearTimeout(me.hideMenuFuture.timer);
            me.hideMenu();
            me.hideMenuFuture = null;
        }
    };

    proto.cancelShowMenu = function () {
        var me = this;
        me.options.trace && console.log('cancelShowMenu @ ' + new Date());
        if (me.showMenuFuture) {
            clearTimeout(me.showMenuFuture.timer);
            me.showMenuFuture = null;
        }
    };

    proto.cancelHideMenu = function () {
        var me = this;
        me.options.trace && console.log('cancelHideMenu @ ' + new Date());
        if (!me.hideMenuFuture) {
            return;
        }
        var timer = me.hideMenuFuture.timer;
        clearTimeout(timer);
        me.hideMenuFuture = null;
    };

    proto.showMenu = function ($li) {
        var me = this;
        me.options.trace && console.log('showMenu @ ' + new Date());
        me.showMenuFuture = null;
        if ($li.hasClass('menuShown')) {
            return;
        }
        $li.addClass('menuShown');
        var $ul = $li.find('>ul');

        var liPos = $li.offset();
        var x = liPos.left + $li.outerWidth() - 5;
        var y = liPos.top - 3;

        // measure first
        $ul.css({
            left: '-1024px',
            top: '-2048px'
        }).show();
        var w = $ul.outerWidth();
        var h = $ul.outerHeight();
        var viewport = domex.getViewport();
        if (x + w > viewport.x2) {
            x = liPos.left - w + 3;
        }
        if (y + h > viewport.y2) {
            y = liPos.top + $li.outerHeight() - h + 3;
        }

        y = me.fitMenuHeightInViewport($ul, y);

        //
        var useFixedLayout = $li.parent().is('.scrollable');
        if (useFixedLayout) {
            console.log('use fixed layout for ' + jsex.trim($li.contents(':first').text()) + '\'s menu');
            x -= viewport.x1;
            y -= viewport.y1;
        }
        else {
            x -= liPos.left;
            y -= liPos.top;
        }

        //
        var css = {
            left: x + 'px',
            top: y + 'px',
            position: useFixedLayout ? 'fixed' : 'absolute'
        };

        $ul.css(css);
        me.$lastShownMenu = $ul;
    };

    proto.makeScrollableMenu = function ($ul) {
        var me = this;
        var $upScroller = $('<li class="up scroller"><span></span></li>');
        $upScroller.prependTo($ul);

        var $downScroller = $('<li class="down scroller"><span></span></li>');
        $downScroller.appendTo($ul);

        var config = {
            hoverInterval: 200,
            interval: 50
        };
        $downScroller.repeatButton(config).on('click', function () {
            me.scrollMenu($ul, 1);
        });
        $upScroller.repeatButton(config).on('click', function () {
            me.scrollMenu($ul, -1);
        });
    };

    proto.scrollMenu = function ($ul, dir) {
        var me = this;

        var $upScroller = $ul.children('.scroller.up');
        var $downScroller = $ul.children('.scroller.down');

        var scrollTop = $ul.scrollTop();
        scrollTop += 10 * dir;
        var totalHeight = $ul[0].scrollHeight;
        var viewportHeight = $ul.height();
        if (scrollTop + viewportHeight > totalHeight) {
            scrollTop = totalHeight - viewportHeight;
//            $downScroller.hide();
            $downScroller.css({visibility: 'hidden'});
            $downScroller.addClass('disabled');
        }
        else {
//            $downScroller.show();
            $downScroller.removeClass('disabled');
            $downScroller.css({height: '6px', visibility: 'visible'});
        }
        if (scrollTop <= 0) {
            $upScroller.hide();
            scrollTop = 0;
        }
        else {
            $upScroller.show();
        }
        $upScroller.css('top', scrollTop + 'px');
        $downScroller.css('bottom', -scrollTop + 'px');
        $ul.scrollTop(scrollTop);
    };

    proto.hideTopLevelMenu = function () {
        var me = this;
        var $menu = me.$lastShownMenu;
        if (!$menu) {
            return;
        }
        if ($menu.is(me.$ul)) {
            me.hide();
            return;
        }
        var $parentLi = $menu.parent();
        $parentLi.removeClass('menuShown').addClass('hovered');
        $menu.hide();
        $menu.removeClass('active');

        var $newTopLevelMenu = $parentLi.parent();
        me.$activeMenu = me.$lastShownMenu = $newTopLevelMenu;
    };

    proto.hideMenu = function () {
        var me = this;
        me.options.trace && console.log('hideMenu @ ' + new Date());
        me.hideMenuFuture = null;

        var $menu = me.$lastShownMenu;
        while ($menu && $menu.length && !$menu.is(me.$activeMenu) && !$menu.is(me.$ul)) {

            var $parentLi = $menu.parent();
            $parentLi.removeClass('menuShown').removeClass('hovered');

            if ($menu.hasClass('scrollable')) {
                $menu.scrollTop(0)
                    .removeClass('scrollable')
                    .css('height', 'auto');
                $menu.children('.scroller').remove();
            }

            //
            $menu.hide();
            $menu.removeClass('active');
            $menu = $parentLi.parent();
        }
        me.$lastShownMenu = me.$ul.is(me.$activeMenu) ? null : me.$activeMenu;
    };

    proto.setupParentMenuItem = function ($li) {
        var me = this;
        var $ul = $li.children('ul');
        $ul.hide();
        $ul.addClass('menu');
        $li.addClass('hasMenu');
        $('<div class="arrow"></div>').appendTo($li);
    };

    proto.fitMenuHeightInViewport = function ($ul, y) {
        var me = this;
        var viewport = domex.getViewport();
        if (y < viewport.y1) {
            var h = $ul.outerHeight();
            y = viewport.y2 - h;
            if (y < viewport.y1) {
                y = viewport.y1 + 3;
                var space = 6;
                space = 12;
                var viewportH = (viewport.y2 - viewport.y1 - space);
                $ul.addClass('scrollable').css({
                    height: viewportH + 'px'
                });
                me.makeScrollableMenu($ul);
            }
        }
        return y;
    };

    //
    proto.showAt = function (x, y) {
        var me = this;

        // measure first
        me.$ul.css({
            left: '-1024px',
            top: '-2048px'
        });

        var viewport = domex.getViewport();

        var w = me.$ul.outerWidth();
        var h = me.$ul.outerHeight();

        if (x + w > viewport.x2) {
            x = viewport.x2 - w;
        }
        if (y + h > viewport.y2) {
            y = viewport.y2 - h;
        }

        //
        y = me.fitMenuHeightInViewport(me.$ul, y);

        me.$ul.css({
            left: x + 'px',
            top: y + 'px'
        }).show();
        me.$lastShownMenu = me.$ul;

//        $(document).on('keydown.popupMenu', function(e) {
//        });
        undecorated.keydownInterceptors.push(function (e) {
            if (me.onKeydown(e)) {
                return true;
            }
            return false;
        });
    };

    //
    proto.registerHandler = function (handlerOrMap) {
        var me = this;
        if (typeof(handlerOrMap) == 'object') {
            for (var action in handlerOrMap) {
                var handler = handlerOrMap[action];
                var $li = me.$ul.find('li[data-action="' + action + '"]');
                $li.on('action', handler);
            }
        }
        else if (typeof(handlerOrMap) == 'function') {
            me.$ul.on('action', handlerOrMap);
        }
    };

    proto.setChecked = function (li, checked) {
        var $li = $(li);
        if ($li.hasClass('separator')) {
            return;
        }
        $li.dataAttr('checked', checked ? 1 : 0);
        var $checkbox = $li.children('div.checkbox');
        if (checked) {
            if (!$checkbox.length) {
                $('<div class="checkbox"></div>').prependTo($li);
            }
        }
        else {
            $checkbox.remove();
        }
    };

    proto.setDisabled = function (li, disabled) {
        var $li = $(li);
        $li.toggleClass('disabled', disabled);
    };

    proto.onKeydown = function (e) {
        var me = this;
        var keyCode = e.which;
        console.log('######## popupMenu.onKeydown: ' + keyCode);
        if (keyCode == 27) {
            me.hideTopLevelMenu();
            return true;
        }
        else if(keyCode == 38) { //u
            me.highlightItem(-1);
            return true;
        }
        else if(keyCode == 40) { //d
            me.highlightItem(1);
            return true;
        }
        else if(keyCode == 39) { //r
            me.expandItem();
            return true;
        }
        else if(keyCode == 37) { //l
            me.collapseItem();
            return true;
        }
        else if(keyCode == 13) {
            me.clickItem();
            return true;
        }
        return false;
    };

    proto.clickItem = function() {
        var me = this;
        var $hovered = me.$lastShownMenu.children('li.hovered');
        if(!$hovered.length) {
            return;
        }
        if($hovered.hasClass('hasMenu')) {
            me.expandItem();
        }
        else {
            me.triggerAction($hovered);
        }
    };

    proto.collapseItem = function() {
        var me = this;
        if(me.$lastShownMenu[0] === me.$ul[0]) {
            return;
        }
        me.hideTopLevelMenu();
    };

    proto.expandItem = function() {
        var me = this;
        var $hovered = me.$lastShownMenu.children('li.hovered');
        if(!$hovered.length || !$hovered.hasClass('hasMenu')) {
            return;
        }
        me.showMenu($hovered);
        me.$lastShownMenu.children('li:first').addClass('hovered');
    };

    proto.highlightItem = function(offset) {
        var me = this;
        var $hovered = me.$lastShownMenu.children('li.hovered');
        $hovered.removeClass('hovered');
        var $li;
        if(offset > 0) {
            $li = $hovered.nextAll(':not(.separator):first');
            if(!$li.length) {
                $li = me.$lastShownMenu.children('li:first');
            }
        }
        else {
            $li = $hovered.prevAll(':not(.separator):first');
            if(!$li.length) {
                $li = me.$lastShownMenu.children('li:last');
            }
        }

        $li.addClass('hovered');
    };

    //
    $.fn.popupMenu = $.addPluginFn('popupMenu', PopupMenu, {
        showDelay: 300,
        hideDelay: 300,
        rootVisible: false,
        trace: false,
        hideOnAction: true
    });

})(jQuery);